package utils;

import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;

import play.mvc.Router;
import play.mvc.Router.ActionDefinition;
import swagger.annotations.ApiInfo;
import swagger.annotations.Param;

public class ApidocGenerate {

    public static class Paths {

        Map<String, Api> paths = new HashMap<String, ApidocGenerate.Api>();
    }

    public static class Api {

        Operation get;
        Operation post;
        Operation delete;
        Operation put;
    }

    public static class Operation {

        String summary;
        String description = "";
        List<Parameter> parameters = new ArrayList<ApidocGenerate.Parameter>();
        List<String> tags = new ArrayList<String>();
        public String[] consumes;
    }

    public static class Parameter {

        String name;
        String in;
        boolean required;
        String type;
        String description;
        String format;
    }

    public static class Swagger {

        String swagger = "2.0";
        Map<String, String> info = new HashMap<String, String>();
        String host = "127.0.0.1:8080";
        String[] schemes = {"http"};
        String basePath = "/rest";
        String[] produces = {"application/json"};
        Map<String, Api> paths;
    }

    private static Swagger output(String host, String basePath, Paths paths) {
        Swagger swagger = new Swagger();
        swagger.host = host;
        swagger.basePath = basePath;
        swagger.paths = paths.paths;
        return swagger;
    }

    public static Swagger scanClasses(String host, String basePath,
            List<Class<?>> clzs) {
        Paths paths = new Paths();
        for (Class<?> clz : clzs) {
            scanClass(paths, clz);
        }
        return output(host, basePath, paths);
    }

    public static Swagger scanClasses(String host, String basePath,
            Class<?>... clzs) {
        Paths paths = new Paths();
        scanClasses(paths, clzs);
        return output(host, basePath, paths);
    }

    private static void scanClasses(Paths paths, Class<?>... clzs) {
        for (Class<?> clz : clzs) {
            scanClass(paths, clz);
        }
    }

    private static void scanClass(Paths paths, Class<?> clz) {
        Stream<Method> methods = Stream.of(clz.getDeclaredMethods()).filter(
                m -> Modifier.isStatic(m.getModifiers())
                && Modifier.isPublic(m.getModifiers())
                && m.getAnnotation(ApiInfo.class) != null);
        methods.forEach(m -> {
            ActionDefinition r = Router.reverse(clz.getName() + "."
                    + m.getName());
            String path = r.url;
            Api api = null;
            api = (paths.paths.getOrDefault(path, new Api()));
            paths.paths.put(path, api);

            Operation oper = new Operation();
            oper.tags.add(clz.getName());

            ApiInfo ma = m.getAnnotation(ApiInfo.class);
            if (ma != null) {
                oper.summary = ma.desc();
                oper.description = ma.desc();
                oper.consumes = ma.consumes();
            }
            Stream.of(m.getParameters()).forEach(
                    p -> {
                        Param pa = p.getAnnotation(Param.class);
                        Parameter param = new Parameter();
                        if (pa != null) {
                            param.name = (pa != null && pa.name() != null) ? pa
                                    .name() : p.getName();
                            param.in = pa != null ? pa.in() : "query";
                            param.description = pa.desc();
                        }
                        String lowerCase = p.getType().getSimpleName().toLowerCase();
                        switch (lowerCase) {
                            case "int":
                                param.type = "integer";
                                break;
                            default:
                                param.type = lowerCase;
                        }
                        oper.parameters.add(param);
                    });
            switch (r.method) {
                case "GET":
                    api.get = oper;
                    break;
                case "POST":
                    api.post = oper;
                    break;
                case "PUT":
                    api.put = oper;
                    break;
                case "DELETE":
                    api.delete = oper;
                    break;
            }
        });
    }
}
